這篇教學會延伸「Mediapipe 手掌辨識」和「在影片中即時繪圖」文章,並應用「OpenCV 影像遮罩」功能,實作一個「用手指擦除鏡子霧氣」的趣味效果 ( 食指和中指分開時不會擦除,食指中指併攏就會擦除 )。
因為程式使用 Jupyter 搭配 Tensorflow 進行開發,所以請先閱讀「使用 Anaconda」和「使用 MediaPipe」,安裝對應的套件,如果不要使用 Juputer,也可參考「使用 Python 虛擬環境」,建立虛擬環境進行實作。
參考「影像遮罩」文章,實作「清楚影像裡面,有一個模糊區域」的效果,過程的原理如下:
- 使用 NumPy 產生兩個遮罩,一個遮罩給「清楚的影像」,一個遮罩給「模糊的影像」。
- 清楚影像的遮罩,需要套用模糊的部分為黑色,其他為白色。
- 模糊影像的遮罩,需要套用模糊的部分為白色,其他為黑色。
- 將遮罩轉換為灰階後,使用 cv2.bitwise_and 方法套用遮罩。
- 遮罩套用完成,使用 cv2.add 方法合併影像。
下方的程式碼執行後,會在攝影機的影像中,即時套用遮罩的效果。
import cv2
import numpy as np
w = 640 # 定義影片寬度
h = 360 # 定義影像高度
dots = [] # 記錄座標
mask_b = np.zeros((h,w,3), dtype='uint8') # 產生黑色遮罩 -> 套用清楚影像
mask_b[:, :] = 255 # 設定黑色遮罩底色為白色
mask_b[80:280, 220:420] = 0 # 設定黑色遮罩哪個區域是黑色
mask_w = np.zeros((h,w,3), dtype='uint8') # 產生白色遮罩 -> 套用模糊影像
mask_w[80:280, 220:420] = 255 # 設定白色遮罩哪個區域是白色
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while True:
ret, img = cap.read()
if not ret:
print("Cannot receive frame")
break
img = cv2.resize(img,(w,h)) # 縮小尺寸,加快速度
img = cv2.flip(img, 1) # 翻轉影像
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) # 轉換顏色為 BGRA ( 計算時需要用到 Alpha 色版 )
img2 = img.copy() # 複製影像
img2 = cv2.blur(img, (55, 55)) # 套用模糊
mask1 = cv2.cvtColor(mask_b, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img = cv2.bitwise_and(img, img, mask=mask1) # 清楚影像套用黑遮罩
mask2 = cv2.cvtColor(mask_w, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img2 = cv2.bitwise_and(img2, img2, mask=mask2) # 模糊影像套用白遮罩
output = cv2.add(img, img2) # 合併影像
cv2.imshow('oxxostudio', output)
if cv2.waitKey(50) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
了解模糊和清楚合併的原理後,參考「在影片中即時繪圖」文章,將滑鼠繪圖的程式加入到程式裡,執行後就可以在模糊的影像中,用滑鼠擦出清楚的影像。
import cv2
import numpy as np
w = 640 # 定義影片寬度
h = 360 # 定義影像高度
dots = [] # 記錄座標
mask_b = np.zeros((h,w,3), dtype='uint8') # 產生黑色遮罩 -> 套用清楚影像
mask_w = np.zeros((h,w,3), dtype='uint8') # 產生白色遮罩 -> 套用模糊影像
mask_w[0:h, 0:w] = 255 # 白色遮罩背景為白色
# 滑鼠繪圖函式
def show_xy(event,x,y,flags,param):
global dots, mask
if flags == 1:
if event == 1:
dots.append([x,y])
if event == 4:
dots = []
if event == 0 or event == 4:
dots.append([x,y])
x1 = dots[len(dots)-2][0]
y1 = dots[len(dots)-2][1]
x2 = dots[len(dots)-1][0]
y2 = dots[len(dots)-1][1]
cv2.line(mask_w, (x1,y1), (x2,y2), (0,0,0), 50) # 在白色遮罩上畫出黑色線條
cv2.line(mask_b, (x1,y1), (x2,y2), (255,255,255), 50) # 在黑色遮罩上畫出白色線條
cv2.imshow('oxxostudio', mask) # 啟用視窗
cv2.setMouseCallback('oxxostudio', show_xy) # 偵測滑鼠行為
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Cannot open camera")
exit()
while True:
ret, img = cap.read()
if not ret:
print("Cannot receive frame")
break
img = cv2.resize(img,(w,h)) # 縮小尺寸,加快速度
img = cv2.flip(img, 1) # 翻轉影像
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) # 轉換顏色為 BGRA ( 計算時需要用到 Alpha 色版 )
img2 = img.copy() # 複製影像
img2 = cv2.blur(img, (55, 55)) # 套用模糊
mask1 = cv2.cvtColor(mask_b, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img = cv2.bitwise_and(img, img, mask=mask1) # 清楚影像套用黑遮罩
mask2 = cv2.cvtColor(mask_w, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img2 = cv2.bitwise_and(img2, img2, mask=mask2) # 模糊影像套用白遮罩
output = cv2.add(img, img2) # 合併影像
cv2.imshow('oxxostudio', output)
if cv2.waitKey(50) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
參考「Mediapipe 手掌偵測」文章,偵測手指的座標,就能透過手指擦除模糊的區域,為了區隔「什麼時候要擦」,可以計算「食指和中指的距離」,如果距離比較大就不擦 ( 手勢 YA ),距離比較小就擦 ( 兩隻手指併攏 )。
import cv2
import mediapipe as mp
import numpy as np
import math
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands
cap = cv2.VideoCapture(0) # 讀取攝影機
# mediapipe 啟用偵測手掌
with mp_hands.Hands(
model_complexity=0,
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as hands:
if not cap.isOpened():
print("Cannot open camera")
exit()
w = 640 # 定義影片寬度
h = 360 # 定義影像高度
dots = [] # 記錄座標
mask_b = np.zeros((h,w,3), dtype='uint8') # 產生黑色遮罩 -> 套用清楚影像
mask_w = np.zeros((h,w,3), dtype='uint8') # 產生白色遮罩 -> 套用模糊影像
mask_w[0:h, 0:w] = 255 # 白色遮罩背景為白色
while True:
ret, img = cap.read()
img = cv2.resize(img, (w,h)) # 縮小尺寸,加快處理效率
img = cv2.flip(img, 1) # 翻轉影像
img_hand = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 偵測手勢使用
img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) # 轉換顏色為 BGRA ( 計算時需要用到 Alpha 色版 )
img2 = img.copy() # 複製影像
img2 = cv2.blur(img, (55, 55)) # 套用模糊
if not ret:
print("Cannot receive frame")
break
results = hands.process(img_hand) # 偵測手勢
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
finger_points = [] # 記錄手指節點位置的串列
for i in hand_landmarks.landmark:
x = i.x
y = i.y
finger_points.append((x,y)) # 記錄手指節點位置
if finger_points:
fx1 = finger_points[8][0]
fy1 = finger_points[8][1]
fx2 = finger_points[12][0]
fy2 = finger_points[12][1]
d = ((fx1-fx2)*(fx1-fx2)+(fy1-fy2)*(fy1-fy2))**0.5 # 計算食指和中指分開的距離
if d<0.15:
dots.append([fx1,fy1])
dl = len(dots)
if dl>1:
x1 = int(dots[dl-2][0]*w) # 計算出真正的座標
y1 = int(dots[dl-2][1]*h)
x2 = int(dots[dl-1][0]*w)
y2 = int(dots[dl-1][1]*h)
cv2.line(mask_w, (x1,y1), (x2,y2), (0,0,0), 50) # 在白色遮罩上畫出黑色線條
cv2.line(mask_b, (x1,y1), (x2,y2), (255,255,255), 50) # 在黑色遮罩上畫出白色線條
else:
dots = []
mask1 = cv2.cvtColor(mask_b, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img = cv2.bitwise_and(img, img, mask=mask1) # 清楚影像套用黑遮罩
mask2 = cv2.cvtColor(mask_w, cv2.COLOR_BGR2GRAY) # 轉換遮罩為灰階
img2 = cv2.bitwise_and(img2, img2, mask=mask2) # 模糊影像套用白遮罩
output = cv2.add(img, img2) # 合併影像
cv2.imshow('oxxostudio', output)
keyboard = cv2.waitKey(5)
if keyboard == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
大家好,我是 OXXO,是個即將邁入中年的斜槓青年,我已經寫了超過 400 篇 Python 的教學,有興趣可以參考下方連結呦~ ^_^